package top.codedance.iotp.client.plugins.async.opc;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import top.codedance.iotp.client.ClientApplication;
import top.codedance.iotp.common.executer.AsyncAbstractExecuter;
import top.codedance.iotp.common.entity.CommonEntity;
import org.apache.log4j.Logger;
import org.jinterop.dcom.common.JIException;
import org.jinterop.dcom.common.JISystem;
import org.jinterop.dcom.core.JIUnsignedInteger;
import org.jinterop.dcom.core.JIUnsignedShort;
import org.jinterop.dcom.core.JIVariant;
import org.openscada.opc.lib.common.ConnectionInformation;
import org.openscada.opc.lib.da.*;
import org.openscada.opc.lib.list.ServerList;

import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.Executors;

public class DaOpcService extends Service {
    private Logger logger = ClientApplication.getLogger(DaOpcService.class);
    private Server s;
    private boolean status = false;
    private volatile Set<String> subscribeTags = new LinkedHashSet<>();
    private Timer t = new Timer();

    public DaOpcService(AsyncAbstractExecuter executer, OpcServiceParam param) {
        super(executer, param);
        t.schedule(new TimerTask() {
            @Override
            public void run() {
                List<JSONObject> readArray = new ArrayList<>();
                for(String tag : subscribeTags){
                    JSONObject jo = new JSONObject();
                    jo.put("tag", tag);
                    readArray.add(jo);
                }
                JSONArray results = new JSONArray();
                reads(readArray, results);
                JSONObject result;
                for (int i = 0;i<results.size();i++){
                    result = results.getJSONObject(i);
                    receipt(result.getString("id"), result.get("value"));
                }
            }
        }, 1000, 1000);
    }

    /**
     * 创建联接
     */
    @Override
    public synchronized void createConnect() throws Exception {
        JISystem.setAutoRegisteration(true);
        ConnectionInformation connectionInfo = new ConnectionInformation();
        connectionInfo.setHost(param.getHost());
        connectionInfo.setUser(param.getUser());
        connectionInfo.setDomain(param.getDomain());
        connectionInfo.setPassword(param.getPassword());
        ServerList serverList = new ServerList(param.getHost(), param.getUser(), param.getPassword(), param.getDomain());
        String clsId = serverList.getClsIdFromProgId(param.getProgId());
        connectionInfo.setClsid(clsId);
        s = new Server(connectionInfo, Executors.newSingleThreadScheduledExecutor());
        s.addStateListener(status -> DaOpcService.this.status = status);
        AutoReconnectController autos = new AutoReconnectController(s, 1000 * 60 * 60);
        autos.connect();
    }

    @Override
    public void subscribe() {
        Set<String> tags = OpcExecuterImpl.tasks.keySet();
        subscribeTags.clear();
        for (String tag : tags){
            subscribeTags.add(tag);
        }
    }

    @Override
    public void close() {
        t.cancel();
        s.disconnect();
        s.dispose();
    }

    /**
     * 批量读取
     *
     * @param readArray
     * @param outResult
     */
    private void reads(List<JSONObject> readArray, JSONArray outResult) {
        try {
            Group group = s.addGroup();
            List<String> tags = new ArrayList<>();
            List<Item> items = new ArrayList<>();
            for (JSONObject jo : readArray) {
                tags.add(jo.getString("tag"));
                items.add(group.addItem(jo.getString("tag")));
            }
            Item[] itemArr = items.toArray(new Item[]{});
            Map<Item, ItemState> resultMap = group.read(true, itemArr);
            for (Item key : resultMap.keySet()) {
                ItemState is = resultMap.get(key);
                outResult.add(dumpValue(key.getId(), is.getValue()));
            }
        } catch (Exception ex) {
            logger.debug(ex.getMessage());
        }
    }

    /**
     * 写入单个
     *
     * @param item
     * @param v
     * @throws JIException
     */
    private void write(Item item, Object v) throws JIException {
        JIVariant value = getValue(v);
        try {
            item.write(value);
        } catch (JIException ex) {
            logger.debug(ex.getMessage());
        }
    }

    /**
     * 批量写入
     *
     * @param writeArray
     */
    private void writes(List<JSONObject> writeArray) {
        try {
            Group group = s.addGroup();
            List<String> tags = new ArrayList<>();
            Map<String, Object> values = new HashMap<>();
            List<WriteRequest> requests = new ArrayList<>();
            for (JSONObject jo : writeArray) {
                tags.add(jo.getString("tag"));
                values.put(jo.getString("tag"), jo.get("value"));
                Item item = group.addItem(jo.getString("tag"));
                JIVariant value = getValue(jo.get("value"));
                requests.add(new WriteRequest(item, value));
            }
            WriteRequest[] writeRequestArr = requests.toArray(new WriteRequest[]{});
            group.write(writeRequestArr);
        } catch (Exception ex) {
            logger.debug(ex.getMessage());
        }
    }

    @Override
    public void receipt(String tag, Object body) {
        synchronized (OpcExecuterImpl.class) {
            JSONObject jo = new JSONObject();
            jo.put(tag, body);
            Set<Map.Entry<String, Set<String>>> entries = OpcExecuterImpl.tasks.entrySet();
            for (Map.Entry<String, Set<String>> entry : entries){
                if(entry.getKey().equals(tag)){
                    CommonEntity ce = genCommonEntity();
                    ce.setBody(jo.toJSONString().getBytes(StandardCharsets.UTF_8));
                    Set<String> value = entry.getValue();
                    for (String cid : value){
                        executer.send(ce.build(), cid);
                        logger.debug("Send data to " + cid);
                    }
                    break;
                }
            }
        }
    }

    @Override
    public void send(String tag, int ns, Object value) {
        try {
            Group group = s.addGroup();
            Item item = group.addItem(tag);
            write(item, value);
        }catch (Exception e){
            logger.debug(e.getMessage());
        }
    }

    @Override
    public void send(JSONArray writeArray){
        writes(writeArray);
    }

    @Override
    public boolean isRunning() {
        return status;
    }

    /**
     * 获取值
     *
     * @param v
     * @return
     */
    private JIVariant getValue(Object v) {
        JIVariant value;
        if (v instanceof Boolean) {
            value = new JIVariant((boolean) v);
        } else if (v instanceof Long) {
            value = new JIVariant((long) v);
        } else if (v instanceof Integer) {
            value = new JIVariant((int) v);
        } else if (v instanceof Short) {
            value = new JIVariant((short) v);
        } else if (v instanceof Byte) {
            value = new JIVariant((byte) v);
        } else if (v instanceof Double) {
            value = new JIVariant((double) v);
        } else if (v instanceof Float) {
            value = new JIVariant((float) v);
        } else {
            value = new JIVariant(v.toString());
        }
        return value;
    }

    /**
     * 数据类型解析
     *
     * @param id 标签id
     * @param v  结果
     * @return
     * @throws JIException
     */
    private JSONObject dumpValue(String id, JIVariant v) throws JIException {
        Object value;
        if (!v.isArray()) {
            value = v.getObject();
        } else {
            value = v.getObjectAsArray().toString();
        }
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("id", id);
        if (value instanceof Boolean) {
            jsonObject.put("value", Boolean.valueOf(value.toString()));
        } else if (value instanceof Long || value instanceof Integer || value instanceof Short || value instanceof Byte) {
            jsonObject.put("value", Long.valueOf(value.toString()));
        } else if (value instanceof Double || value instanceof Float) {
            jsonObject.put("value", Double.valueOf(value.toString()));
        } else if (value instanceof JIUnsignedShort) {
            JIUnsignedShort vl = (JIUnsignedShort) value;
            jsonObject.put("value", Long.valueOf(vl.getValue().toString()));
        } else if (value instanceof JIUnsignedInteger) {
            JIUnsignedInteger vi = (JIUnsignedInteger) value;
            jsonObject.put("value", Integer.valueOf(vi.getValue().toString()));
        } else if (value instanceof Date) {
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            jsonObject.put("value", sdf.format(value));
        } else {
            try {
                String str = v.getObjectAsString2();
                jsonObject.put("value", str.isEmpty() ? null : str);
            } catch (Exception e) {
                jsonObject.put("value", v.getObject());
            }
        }
        jsonObject.put("good", true);
        return jsonObject;
    }
}
